home *** CD-ROM | disk | FTP | other *** search
Wrap
// // IsoController.m // Isolator // // Created by Ben Willmore on 08/02/2007. // Copyright 2007 __MyCompanyName__. All rights reserved. // /* FIXME: * Add option to blacken only main display * Does hotpluggin monitors work correctly? regardless of the state of hidebackgroundapps?? */ #import "Carbon/Carbon.h" #import <HIToolbox/MacApplication.h> #import "IsoController.h" @implementation IsoController static kNoReason = 0; static kAppSwitched = 1; static kEnteredIsolateMode = 2; static kCentury = 100; static IsoController *me; pascal OSStatus appSwitched (EventHandlerCallRef nextHandler, EventRef theEvent, void* userData) // handle change in frontmost app { //NSLog(@"app switched"); [me setReason:kAppSwitched]; [me isolate]; return eventNotHandledErr; } OSStatus hotKeyHandler(EventHandlerCallRef nextHandler,EventRef theEvent, void *userData) // handle hotkey press { EventHotKeyID hkCom; GetEventParameter(theEvent,kEventParamDirectObject,typeEventHotKeyID,NULL,sizeof(hkCom),NULL,&hkCom); int l = hkCom.id; switch (l) { case 1: // isolator hotkey [me toggle]; break; case 2: // prefs win hotkey [me openPrefs]; break; } return noErr; } -(id) init { [super init]; // register for Carbon event on app switching me = self; sparkleUpdater = [[SUUpdater alloc] init]; reason = kNoReason; EventTypeSpec eventType; eventType.eventClass = kEventClassApplication; eventType.eventKind = kEventAppFrontSwitched; EventHandlerUPP handlerUPP = NewEventHandlerUPP(appSwitched); InstallApplicationEventHandler (handlerUPP, 1, &eventType, self, NULL); DisposeEventHandlerUPP(appSwitched); // set up hotkey for prefs window -- remember asynckeys for key codes EventHotKeyID gMyHotKeyID; //EventTypeSpec eventType; eventType.eventClass=kEventClassKeyboard; eventType.eventKind=kEventHotKeyPressed; gMyHotKeyID.signature='htk2'; gMyHotKeyID.id=2; // cmd-opt-shift-i for prefs window RegisterEventHotKey(34, cmdKey + optionKey + shiftKey, gMyHotKeyID, GetApplicationEventTarget(), 0, &gMyHotKeyRef); // read preferences NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; if (![defaults objectForKey:@"StartupMode"]) [defaults setInteger:1 forKey:@"StartupMode"]; if ([defaults integerForKey:@"StartupMode"]==0) active = YES; else active = NO; if (![defaults objectForKey:@"HideDesktop"]) [defaults setBool:YES forKey:@"HideDesktop"]; if (![defaults objectForKey:@"HideBackgroundApps"]) [defaults setInteger:NO forKey:@"HideBackgroundApps"]; if (![defaults objectForKey:@"HideOnMainScreenOnly"]) [defaults setInteger:NO forKey:@"HideOnMainScreenOnly"]; if ([defaults integerForKey:@"Hotkey"]==0) { [defaults setInteger:34 forKey:@"Hotkey"]; // cmd-shift-i [defaults setInteger:NSCommandKeyMask+NSShiftKeyMask forKey:@"HotkeyFlags"]; } if (![defaults objectForKey:@"ShowMenubarIcon"]) [defaults setBool:YES forKey:@"ShowMenubarIcon"]; if (![defaults objectForKey:@"MouseClickEffect"]) [defaults setInteger:1 forKey:@"MouseClickEffect"]; if (![defaults objectForKey:@"NumberOfTimesUsed"]) [defaults setInteger:0 forKey:@"NumberOfTimesUsed"]; if (![defaults objectForKey:@"SUScheduledCheckInterval"]) [defaults setInteger:86400 forKey:@"SUScheduledCheckInterval"]; shownCenturyMessage = NO; if ([defaults integerForKey:@"NumberOfTimesUsed"]>=kCentury) shownCenturyMessage = YES; // startupitem startupItemController = [[StartupItemController alloc] init]; // status bar control [self setupStatusItem]; gMyHotKeyRef = 0; // value transformer LessThanAboutOne *lessThanAboutOne; lessThanAboutOne = [[[LessThanAboutOne alloc] init] autorelease]; [NSValueTransformer setValueTransformer:lessThanAboutOne forName:@"LessThanAboutOne"]; [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidChangeScreenParameters:) name:NSApplicationDidChangeScreenParametersNotification object:nil]; lastProcessSerial.highLongOfPSN = 0; lastProcessSerial.lowLongOfPSN = kNoProcess; ignorePID = -1; return self; } -(void) awakeFromNib { [self setupBlackWindows]; [self setKeyCombo]; } -(void) setKeyCombo { KeyCombo keyCombo; NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; keyCombo.code = [defaults integerForKey:@"Hotkey"]; keyCombo.flags = [defaults integerForKey:@"HotkeyFlags"]; [shortcutRecorder setKeyCombo:keyCombo]; } -(void) saveKeyCombo { KeyCombo keyCombo = [shortcutRecorder keyCombo]; NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; [defaults setInteger:keyCombo.code forKey:@"Hotkey"]; [defaults setInteger:keyCombo.flags forKey:@"HotkeyFlags"]; [self syncDefaults:self]; } -(void) setupStatusItem { NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; if ([defaults boolForKey:@"ShowMenubarIcon"]==NO) { if (statusItem) { [[NSStatusBar systemStatusBar] removeStatusItem:statusItem]; [statusItem release]; } statusItem = nil; return; } if (statusItem) { return; } [self initStatusMenu]; statusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength:22.] retain]; NSRect theFrame = [[statusItem view] frame]; [statusItem setView:[[[IsoStatusItemView alloc] initWithFrame:theFrame isoController:self] autorelease]]; [[statusItem view] setNeedsDisplay:YES]; [statusItem setHighlightMode:YES]; } -(NSStatusItem*) getStatusItem { return statusItem; } -(void) initStatusMenu { statusMenu = [[NSMenu alloc] initWithTitle:@"Isolator"]; [statusMenu setDelegate:self]; if (active) toggleMenuItem = [[NSMenuItem alloc] initWithTitle:@"Turn Isolator Off" action:@selector(toggle) keyEquivalent:@""]; else toggleMenuItem = [[NSMenuItem alloc] initWithTitle:@"Turn Isolator On" action:@selector(toggle) keyEquivalent:@""]; [statusMenu insertItem:toggleMenuItem atIndex:0]; [statusMenu insertItem:[NSMenuItem separatorItem] atIndex:1]; NSMenuItem* prefsMenuItem = [[[NSMenuItem alloc] initWithTitle:@"Preferences..." action:@selector(openPrefs) keyEquivalent:@""] autorelease]; [statusMenu insertItem:prefsMenuItem atIndex:2]; [statusMenu insertItem:[NSMenuItem separatorItem] atIndex:3]; NSMenuItem* updateMenuItem = [[[NSMenuItem alloc] initWithTitle:@"Check for updates..." action:@selector(checkForUpdates:) keyEquivalent:@""] autorelease]; [updateMenuItem setTarget:sparkleUpdater]; [statusMenu insertItem:updateMenuItem atIndex:4]; NSMenuItem* aboutMenuItem = [[[NSMenuItem alloc] initWithTitle:@"About Isolator" action:@selector(openAboutPanel) keyEquivalent:@""] autorelease]; [statusMenu insertItem:aboutMenuItem atIndex:5]; NSMenuItem* quitMenuItem = [[[NSMenuItem alloc] initWithTitle:@"Quit" action:@selector(exit) keyEquivalent:@""] autorelease]; [statusMenu insertItem:quitMenuItem atIndex:6]; } -(NSMenu*)getStatusMenu { return statusMenu; } -(void)showStatusMenu { [statusItem popUpStatusItemMenu:statusMenu]; } -(void) applicationDidChangeScreenParameters:(id)object { [self setupBlackWindows]; } -(void) setupBlackWindows { NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; NSArray* screens; if ([defaults boolForKey:@"HideOnMainScreenOnly"]==YES) screens = [[NSArray alloc] initWithObjects: [NSScreen mainScreen], nil]; else screens = [NSScreen screens]; if (blackWindows) { NSEnumerator *blackEnumerator = [blackWindows objectEnumerator]; BlackWindow *blackWindow = nil; while (blackWindow = [blackEnumerator nextObject]) { [blackWindow close]; } [blackWindows release]; } blackWindows = [[NSMutableArray alloc] init]; NSEnumerator *enumerator = [screens objectEnumerator]; NSScreen* screen; BlackWindow* window; while( screen = [enumerator nextObject] ) { window = [[BlackWindow alloc] initWithScreen:screen]; [blackWindows addObject:window]; } //[screens release]; // don't understand why this isn't needed if (active) { [self enterIsolateMode]; } else { NSEnumerator *enumerator = [blackWindows objectEnumerator]; BlackWindow* window; while( window = [enumerator nextObject] ) { [window orderOut:self]; } } } -(void) isolate { if ( !active ) return; NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; NSDictionary* activeApp = [[[[NSWorkspace sharedWorkspace] activeApplication] copy] autorelease]; NSNumber* activePID = [[[activeApp objectForKey:@"NSApplicationProcessIdentifier"] copy] autorelease]; NSNumber* myPID = [NSNumber numberWithInt:[[NSProcessInfo processInfo] processIdentifier]]; if ( (reason==kAppSwitched) && [activePID isEqual:myPID] ) return; if ( [activePID isEqual:[NSNumber numberWithInt:ignorePID]] ) return; ignorePID = [activePID intValue]; serialToSet.highLongOfPSN = [[activeApp objectForKey:@"NSApplicationProcessSerialNumberHigh"] longValue]; serialToSet.lowLongOfPSN = [[activeApp objectForKey:@"NSApplicationProcessSerialNumberLow"] longValue]; if ([defaults boolForKey:@"HideBackgroundApps"]) { // then hide all the background apps NSArray* launchedApps = [[[[NSWorkspace sharedWorkspace] launchedApplications] copy] autorelease]; NSEnumerator* appEnum = [launchedApps objectEnumerator]; NSDictionary* app = nil; NSNumber* pid = nil; ProcessSerialNumber psn; ProcessInfoRec info; info.processInfoLength = sizeof(ProcessInfoRec); info.processName = nil; FSSpec tempFSSpec; info.processAppSpec = &tempFSSpec; while (app = [appEnum nextObject]) { pid = [app objectForKey:@"NSApplicationProcessIdentifier"]; if (![pid isEqual:activePID]) { psn.highLongOfPSN = [[app objectForKey:@"NSApplicationProcessSerialNumberHigh"] longValue]; psn.lowLongOfPSN = [[app objectForKey:@"NSApplicationProcessSerialNumberLow"] longValue]; if (GetProcessInformation(&psn, &info) == noErr) { ShowHideProcess(&psn,FALSE); } } } // this kludge makes cmd-tabbing work correctly. without it, sometimes the frontmost application doesn't move to // the left of the bar as it should [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(setFrontmost:) userInfo:nil repeats:NO]; } else { //[NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:@selector(setFrontmostAndRepositionBlackWindows:) userInfo:nil repeats:NO]; [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(setFrontmostToApp:) userInfo:nil repeats:NO]; } } -(void) setFrontmost:(id)dummy { NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; if ([defaults boolForKey:@"HideDesktop"] && (reason==kEnteredIsolateMode)) { NSEnumerator *enumerator = [blackWindows objectEnumerator]; BlackWindow* window; while( window = [enumerator nextObject] ) { [window fadeInOrOut:YES]; } } ProcessSerialNumber mySerial; GetCurrentProcess(&mySerial); SetFrontProcess(&mySerial); SetFrontProcess(&serialToSet); } -(void) setFrontmostToMe:(id)dummy { NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; if ([defaults boolForKey:@"HideDesktop"] && (reason==kEnteredIsolateMode)) { NSEnumerator *enumerator = [blackWindows objectEnumerator]; BlackWindow* window; while( window = [enumerator nextObject] ) { [window fadeInOrOut:YES]; } } ProcessSerialNumber mySerial; GetCurrentProcess(&mySerial); SetFrontProcess(&mySerial); [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:@selector(setFrontmostToApp:) userInfo:nil repeats:NO]; } -(void) setFrontmostToApp:(id)dummy { SetFrontProcess(&serialToSet); CGSConnection cid; CGSGetConnectionIDForPSN(0, &serialToSet, &cid); ProcessSerialNumber myPSN; CGSConnection myCid; GetCurrentProcess(&myPSN); CGSGetConnectionIDForPSN(0, &myPSN, &myCid); int nWindows; CGSGetOnScreenWindowCount(myCid, cid, &nWindows); CGSWindow list[nWindows]; CGSGetOnScreenWindowList(myCid, cid, nWindows, list, &nWindows); // special case for Finder which owns Desktop window ProcessInfoRec info; unsigned char processName[64]; info.processInfoLength = sizeof(ProcessInfoRec); info.processName = processName; FSSpec tempFSSpec; info.processAppSpec = &tempFSSpec; if (GetProcessInformation(&serialToSet, &info) != noErr) return; NSString *processNameStr = (NSString*) CFStringCreateWithPascalString(kCFAllocatorDefault, processName, kCFStringEncodingASCII); if ([processNameStr isEqual:@"Finder"]) { NSLog(@"%d",nWindows); nWindows = nWindows-1; } [processNameStr release]; NSEnumerator *enumerator = [blackWindows objectEnumerator]; BlackWindow* window; while( window = [enumerator nextObject] ) { if (nWindows==0) [window orderFrontRegardless]; else [window orderWindow:NSWindowBelow relativeTo:list[nWindows-1]]; } } -(void) setFrontmostAndRepositionBlackWindows:(id)dummy { [self setFrontmost:self]; CGSConnection cid; CGSGetConnectionIDForPSN(0, &serialToSet, &cid); ProcessSerialNumber myPSN; CGSConnection myCid; GetCurrentProcess(&myPSN); CGSGetConnectionIDForPSN(0, &myPSN, &myCid); int nWindows; CGSGetOnScreenWindowCount(myCid, cid, &nWindows); CGSWindow list[nWindows]; CGSGetOnScreenWindowList(myCid, cid, nWindows, list, &nWindows); // special case for Finder which owns Desktop window ProcessInfoRec info; unsigned char processName[64]; info.processInfoLength = sizeof(ProcessInfoRec); info.processName = processName; FSSpec tempFSSpec; info.processAppSpec = &tempFSSpec; if (GetProcessInformation(&serialToSet, &info) != noErr) return; NSString *processNameStr = (NSString*) CFStringCreateWithPascalString(kCFAllocatorDefault, processName, kCFStringEncodingASCII); if ([processNameStr isEqual:@"Finder"]) { nWindows = 0; } [processNameStr release]; NSEnumerator *enumerator = [blackWindows objectEnumerator]; BlackWindow* window; while( window = [enumerator nextObject] ) { if (nWindows==0) [window orderFrontRegardless]; else [window orderWindow:NSWindowBelow relativeTo:list[nWindows-1]]; } } -(void) enterIsolateMode { if (!shownCenturyMessage) { NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; int numTimes = [defaults integerForKey:@"NumberOfTimesUsed"]+1; [defaults setInteger:numTimes forKey:@"NumberOfTimesUsed"]; if (numTimes==kCentury) { [self showCenturyMessage]; shownCenturyMessage = YES; return; } } ignorePID = -1; active = YES; [[statusItem view] setNeedsDisplay:YES]; [toggleMenuItem setTitle:@"Turn Isolator Off"]; // level depends on whether we're hiding other apps or not NSEnumerator *enumerator = [blackWindows objectEnumerator]; BlackWindow* window; while( window = [enumerator nextObject] ) { [window setLevelAsAppropriate]; } reason = kEnteredIsolateMode; [self isolate]; } -(void) leaveIsolateMode { ignorePID = -1; active = NO; NSEnumerator *enumerator = [blackWindows objectEnumerator]; BlackWindow* window; while( window = [enumerator nextObject] ) { [window fadeInOrOut:NO]; } [[statusItem view] setNeedsDisplay:YES]; [toggleMenuItem setTitle:@"Turn Isolator On"]; } -(void) toggle { if (active) [self leaveIsolateMode]; else [self enterIsolateMode]; } -(void) openPrefs { [self willChangeValueForKey:@"startupItemEnabled"]; [self didChangeValueForKey:@"startupItemEnabled"]; [NSApp activateIgnoringOtherApps:YES]; [prefWindow makeKeyAndOrderFront:self]; } -(IBAction)setBackgroundColor:(id)sender { [self syncDefaults:self]; NSEnumerator *enumerator = [blackWindows objectEnumerator]; BlackWindow* window; while( window = [enumerator nextObject] ) { [window setColor]; } [[statusItem view] setNeedsDisplay:YES]; } -(IBAction)setOpacity:(id)sender { [self syncDefaults:self]; NSEnumerator *enumerator = [blackWindows objectEnumerator]; BlackWindow* window; while( window = [enumerator nextObject] ) { [window setOpacity]; } [[statusItem view] setNeedsDisplay:YES]; } -(IBAction)setClickThrough:(id)sender { [self syncDefaults:self]; NSEnumerator *enumerator = [blackWindows objectEnumerator]; BlackWindow* window; while( window = [enumerator nextObject] ) { [window setClickThrough]; } } -(IBAction)setMenuBarIcon:(id)sender { [self syncDefaults:self]; [self setupStatusItem]; } -(IBAction)setHideBackgroundApps:(id)sender { [self syncDefaults:self]; [self leaveIsolateMode]; } -(IBAction)setUpdatesIncludeBetaVersions:(id)sender { NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; if ([defaults boolForKey:@"UpdatesIncludeBetaVersions"]) [defaults setObject:@"http://willmore.eu/software/isolator/allversions.xml" forKey:@"SUFeedURL"]; else [defaults setObject:@"http://willmore.eu/software/isolator/releases.xml" forKey:@"SUFeedURL"]; [self syncDefaults:self]; } -(IBAction)setWindow:(id)sender { [self syncDefaults:self]; if (!active) return; NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; if ([defaults boolForKey:@"HideDesktop"]) { NSEnumerator *enumerator = [blackWindows objectEnumerator]; BlackWindow* window; while( window = [enumerator nextObject] ) { [window fadeInOrOut:YES]; } } else { NSEnumerator *enumerator = [blackWindows objectEnumerator]; BlackWindow* window; while( window = [enumerator nextObject] ) { [window fadeInOrOut:NO]; } } } -(void) registerHotkey:(KeyCombo)keyCombo { if (gMyHotKeyRef>0) { UnregisterEventHotKey(gMyHotKeyRef); } // set up hotkey -- remember asynckeys for key codes EventHotKeyID gMyHotKeyID; EventTypeSpec eventType; eventType.eventClass=kEventClassKeyboard; eventType.eventKind=kEventHotKeyPressed; InstallApplicationEventHandler(&hotKeyHandler,1,&eventType,NULL,NULL); gMyHotKeyID.signature='htk1'; gMyHotKeyID.id=1; RegisterEventHotKey([shortcutRecorder keyCombo].code, [shortcutRecorder cocoaToCarbonFlags:[shortcutRecorder keyCombo].flags], gMyHotKeyID, GetApplicationEventTarget(), 0, &gMyHotKeyRef); } - (void)shortcutRecorder:(SRRecorderControl *)aRecorder keyComboDidChange:(KeyCombo)newKeyCombo; { [self registerHotkey:newKeyCombo]; [self saveKeyCombo]; } - (void)syncDefaults:(id)sender { [[NSUserDefaults standardUserDefaults] synchronize]; } -(BOOL)startupItemEnabled { return [startupItemController enabled]; } -(void)setStartupItemEnabled:(BOOL)value { [startupItemController setEnabled:value]; } -(void)openAboutPanel { [NSApp orderFrontStandardAboutPanel:self]; [NSApp activateIgnoringOtherApps:YES]; } - (void)applicationWillTerminate:(NSNotification *)notification { [[NSUserDefaults standardUserDefaults] synchronize]; } -(BOOL)isActive { return active; } -(void)setReason:(int)theReason { reason = theReason; } -(void)showCenturyMessage { NSAlert* alert = [[NSAlert alloc] init]; [alert setMessageText:[NSString stringWithFormat:@"You have used Isolator %d times.",kCentury]]; [alert setInformativeText:@"Please consider donating a few dollars to support its continued development. Either way, you won't see this message again."]; [alert addButtonWithTitle:@"Go to Paypal donation page"]; [alert addButtonWithTitle:@"Don't donate"]; int result = [alert runModal]; if (result==NSAlertFirstButtonReturn) { NSString* urlString = @"https://www.paypal.com/cgi-bin/webscr?cmd=_xclick&business=bdeb%40willmore%2eeu&item_name=Isolator&no_shipping=2&no_note=1&tax=0¤cy_code=GBP&bn=PP%2dDonationsBF&charset=UTF%2d8"; [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:urlString]]; } [alert release]; } -(void) dealloc { [sparkleUpdater release]; [startupItemController release]; [blackWindows release]; [statusMenu release]; [toggleMenuItem release]; [statusItem release]; [mainMenu release]; [prefWindow release]; [shortcutRecorder release]; [super dealloc]; } -(void) exit { [NSApp terminate:self]; } @end